package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.redisson.annotations.Lock;
import com.tianji.common.autoconfigure.redisson.enums.LockStrategy;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.dto.CouponStatus;
import com.tianji.promotion.dto.ExchangeCodeStatus;
import com.tianji.promotion.dto.UserCouponQuery;
import com.tianji.promotion.entity.Coupon;
import com.tianji.promotion.entity.ExchangeCode;
import com.tianji.promotion.entity.UserCoupon;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.vo.CouponVO;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.tianji.common.constants.RedisConstants.EXCHANGE_CODE_BITMAP_KEY;
import static com.tianji.common.constants.RedisConstants.LOCK_KEY;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author xjs
 */
@Service
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    @Autowired
    private ICouponService couponService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IExchangeCodeService codeService;

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private PlatformTransactionManager txManager;
//    方法1：自己注入自己
//    @Autowired
//    private IUserCouponService userCouponService;
//    第二种方式：使用AopContext

    @Override
    public List<UserCoupon> getByUserIdCouponId(Long userId, Long couponId) {
        return this.lambdaQuery().eq(UserCoupon::getUserId, userId).eq(UserCoupon::getCouponId, couponId).list();
    }

    @Override
    // @Transactional(rollbackFor = Exception.class)
    @Lock(name=LOCK_KEY + "#{T(com.tianji.common.utils.UserContext).getUser()}", lockStrategy = LockStrategy.KEEP_RETRY)
    public void receive(Long couponId) {
        // 获取用户
        Long userId = UserContext.getUser();
        // 合法性校验
        // 判断是否存在
        Coupon coupon = couponService.getById(couponId);
        if(coupon == null){
            throw new BizIllegalException("优惠券不存在");
        }
        // 判断优惠券是否是过期
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if(termEndTime == null){
            termEndTime = LocalDateTime.now().plusDays(coupon.getTermDays());
        }
        if(LocalDateTime.now().isAfter(termEndTime)){
            throw new BizIllegalException("优惠券已经过期");
        }
        // 判断状态是否是发行中
        Integer status = coupon.getStatus();
        CouponStatus couponStatus = CouponStatus.of(status);
        if(couponStatus != CouponStatus.ISSUING){
            throw new BizIllegalException("优惠券不是发行中");
        }
        // 判断库存，total_num > issue_num
        if(coupon.getTotalNum() <= coupon.getIssueNum()){
            throw new BizIllegalException("库存不足");
        }
        // 悲观锁，防止一个用户同时发出多个并发请求，导致一个用户抢到多个券
        // synchronized锁是一个jvm级别的单机锁
        //synchronized (userId.toString().intern()){
        // 手动实现了分布式锁
        //MyLock myLock = new MyLock(LOCK_KEY + userId, redisTemplate);
        //RLock lock = redissonClient.getLock(LOCK_KEY + userId);
        //boolean locked = false;
        //try{
           // locked = lock.tryLock(0,5, TimeUnit.SECONDS);
            //if(locked){
                // 以下方式不行，本质上this调用，会导致事务失效。
                //receive(coupon);
                // 以下方式可以：调用代理对象上的方法，事务生效
                //userCouponService.receive(coupon);
                // 以下方式也可以：还是在调用代理对象上的方法，事务生效
                IUserCouponService userCouponService = (IUserCouponService)AopContext.currentProxy();
                userCouponService.receive(coupon);
                //}
            //}else {
               // throw new BizIllegalException("非法请求");
           // }
        //}catch(Exception e){
         //   log.error(ExceptionUtils.getStackTrace(e));
       //     throw new BizIllegalException("服务端异常，请稍后重试");
       // } finally {
            // 释放锁
       //     if(locked){
       //         lock.unlock();
        //    }
       //}
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void receive(Coupon coupon){
        Long userId = UserContext.getUser();
        // 判断用户是否超出领用限制，用户的已经领取数量 < 优惠券的限领数量
        Integer userLimit = coupon.getUserLimit();
        Integer userReceived = getUserReceivedCount(userId, coupon.getId());
        if(userReceived >= userLimit){
            throw new BizIllegalException("超出领用限制");
        }
        // 修改优惠券的已经数量+1
        int updateResult = couponService.incrementIssueNum(coupon.getId(), coupon.getIssueNum());
        if(updateResult <= 0){
            throw new BizIllegalException("库存不足");
        }
        // 添加用户券记录
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(userId);
        userCoupon.setCouponId(coupon.getId());
        // 说明优惠券没有开始时间，而是设置的有效天数
        if(coupon.getTermBeginTime() == null){
            userCoupon.setTermBeginTime(LocalDateTime.now());
            userCoupon.setTermEndTime(LocalDateTime.now().plusDays(coupon.getTermDays()));
        }else{
            userCoupon.setTermBeginTime(coupon.getTermBeginTime());
            userCoupon.setTermEndTime(coupon.getTermEndTime());
        }
        this.save(userCoupon);
        // throw new RuntimeException("业务异常");
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void exchangeCoupon(String code) {
        Long userId = UserContext.getUser();
        // 兑换码的合法性校验
        //   解析兑换码，得到兑换码的序列号，也是兑换码的id
        long exchangeCodeId = CodeUtil.parseCode(code);
        //   访问redis的bitmap，判断是否已经兑换过了
        String key = EXCHANGE_CODE_BITMAP_KEY;
        Boolean oldValue = redisTemplate.opsForValue().setBit(key, exchangeCodeId, true);
        if(oldValue != null && oldValue){
            //   如果已经兑换过了，抛异常
            throw new BizIllegalException("不能重复兑换");
        }
        try{
            // 根据兑换码找到优惠券
            ExchangeCode exchangeCode = codeService.getById(exchangeCodeId);
            AssertUtils.isNotNull(exchangeCode, "查询不到兑换码");
            Long couponId = exchangeCode.getExchangeTargetId();
            // 判断兑换码是否已经过期
            LocalDateTime expiredTime = exchangeCode.getExpiredTime();
            LocalDateTime now = LocalDateTime.now();
            if(now.isAfter(expiredTime)){
                throw new BizIllegalException("兑换码已经过期");
            }
            // 优惠券的合法性校验
            //    判断是否存在。。。。。。。。
            // 业务逻辑
            //    修改优惠券的已经数量+1
            //    添加用户券记录
            // 以上业务逻辑完全复用领券的有业务逻辑，就相当于领了一个新的券
            receive(couponId);
            //    更新兑换码的状态
            codeService.updateExchangeCode(exchangeCodeId, userId, ExchangeCodeStatus.USED);
        }catch(Exception e){
            log.error(ExceptionUtils.getStackTrace(e));
            // 还原兑换状态
            redisTemplate.opsForValue().setBit(key, exchangeCodeId, false);
        }

    }

    private Integer getUserReceivedCount(Long userId, Long couponId) {
        Integer count = this.lambdaQuery().eq(UserCoupon::getUserId, userId).eq(UserCoupon::getCouponId, couponId).count();
        if(count == null){
            return 0;
        }
        return count;
    }

    @Override
    public PageDTO<CouponVO> queryMyCouponPage(UserCouponQuery query) {
        // 1.获取当前用户
        Long userId = UserContext.getUser();
        // 2.分页查询用户券
        Page<UserCoupon> page = lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getStatus, query.getStatus())
                .page(query.toMpPage(new OrderItem("term_end_time", true)));
        List<UserCoupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }

        // 3.获取优惠券详细信息
        // 3.1.获取用户券关联的优惠券id
        Set<Long> couponIds = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toSet());
        // 3.2.查询
        List<Coupon> coupons = couponService.listByIds(couponIds);

        // 4.封装VO
        return PageDTO.of(page, BeanUtils.copyList(coupons, CouponVO.class));
    }
}
